/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: DateUtils
 * Author:   zhuqifeng
 * Date:     2018/9/11 11:02
 * Description: 日期工具类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.xlkj.ms.tools.core.utils;

import com.xlkj.ms.tools.core.commons.DateFormatEnums;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 时间、日期工具类
 * @author zhuqifeng
 * @date 2021/11/10 11:57
 * @package com.xlkj.ms.tools.core.utils
 */
public final class DateUtils {


    private final static Logger logger = LoggerFactory.getLogger(DateUtils.class);

    /**
     * 校验字符串是否为时间格式
     *
     * @param date
     * @param format
     * @return
     */
    public static boolean isDateFormat(String date, DateFormatEnums format) {
        if (StringUtils.isBlank(date) || null == format || StringUtils.isBlank(format.getExpression())) {
            return false;
        }
        SimpleDateFormat sf = new SimpleDateFormat(format.getExpression());
        try {
            String time = sf.format(sf.parse(date));
            return date.equals(time);
        } catch (ParseException e) {
            return false;
        }
    }

    /**
     * 时间格式的转换
     *
     * @param sourceTime       待转换的时间
     * @param sourceDateFormat 待转换时间的格式
     * @param targetDateFormat 目标的时间格式
     * @return
     */
    public static String convertTime(String sourceTime, DateFormatEnums sourceDateFormat, DateFormatEnums targetDateFormat) {
        return dateToString(stringToDate(sourceTime, sourceDateFormat), targetDateFormat);
    }

    /**
     * 格式化从mysql数据库中查询出来的datetime字段
     * 这种字段如果用String格式进行接收，往往会有一个.0的结尾
     * 该方法就是去掉该后缀
     * @param time
     * @param dateFormatEnums
     * @return
     */
    public static String formatSqlDate(String time, DateFormatEnums dateFormatEnums){
        try{
            return convertTime(time, dateFormatEnums, dateFormatEnums);
        }catch (Exception e){
            return time;
        }
    }

    /**
     * time1是否在time2之前
     *
     * @param time1
     * @param time2
     * @param format
     * @return
     */
    public static boolean time1BeforeTime2(String time1, String time2, DateFormatEnums format) {
        if (StringUtils.isBlank(time1) || StringUtils.isBlank(time2) || null == format || StringUtils.isBlank(format.getExpression())) {
            return false;
        }
        SimpleDateFormat df = new SimpleDateFormat(format.getExpression());
        try {
            Date dt1 = df.parse(time1);
            Date dt2 = df.parse(time2);
            if (dt1.getTime() < dt2.getTime()) {
                return true;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * time1是否在time2之前
     *
     * @param time1
     * @param time2
     * @param format
     * @return
     */
    public static boolean time1EqualsOrBeforeTime2(String time1, String time2, DateFormatEnums format) {
        if (StringUtils.isBlank(time1) || StringUtils.isBlank(time2) || null == format || StringUtils.isBlank(format.getExpression())) {
            return false;
        }
        SimpleDateFormat df = new SimpleDateFormat(format.getExpression());
        try {
            Date dt1 = df.parse(time1);
            Date dt2 = df.parse(time2);
            if (dt1.getTime() <= dt2.getTime()) {
                return true;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * 获取当前时间
     *
     * @param format
     * @return
     */
    public static String currentTime(DateFormatEnums format) {
        return dateToString(new Date(), format);
    }

    /**
     * 获取当前时间
     * @return
     */
    public static Date currentTime(){
        return new Date();
    }

    /**
     * 是否是当前时间
     *
     * @param today
     * @return
     */
    public static boolean isToday(String today) {
        return StringUtils.isEquals(today, currentTime(DateFormatEnums.YYYY_MM_DD));
    }

    /**
     * 比较time时间是否在当前时间之前
     *
     * @param time   目标时间
     * @param format 时间格式
     * @return
     */
    public static boolean beforeNow(String time, DateFormatEnums format) {
        return time1BeforeTime2(time, currentTime(format), format);
    }

    public static boolean afterNow(String time, DateFormatEnums format) {
        return time1BeforeTime2(currentTime(format), time, format);
    }

    /**
     * 时间转换
     *
     * @param date
     * @param format
     * @return
     */
    public static String dateToString(Date date, DateFormatEnums format) {
        if (null == date) {
            return null;
        }
        SimpleDateFormat df = new SimpleDateFormat(format.getExpression());
        return df.format(date);
    }

    /**
     * 时间转换
     *
     * @param date
     * @param format
     * @return
     */
    public static Date stringToDate(String date, DateFormatEnums format) {
        SimpleDateFormat df = new SimpleDateFormat(format.getExpression());
        try {
            return df.parse(date);
        } catch (ParseException e) {
            throw new IllegalArgumentException("can not transform this date:" + date);
        }
    }

    /**
     * 在time的某个域上加或者减counts数<br/>
     * 例如：在time时间上加上1分钟changeTimes("2018-09-20 15:36:30","yyyy-MM-dd HH:mm:ss", Calendar.MINUTE,1)，得到的结果是：2018-09-20 15:37:30
     *
     * @param time   目标时间
     * @param format time的时间格式
     * @param scope  时间域 例如： {@link Calendar#MONTH}等等
     * @param counts 在当前域上要加减多少数值（如果要减，那么该值可以为负数）
     * @return
     */
    public static String changeTimes(String time, DateFormatEnums format, int scope, int counts) {
        SimpleDateFormat df = new SimpleDateFormat(format.getExpression());
        try {
            Date date = df.parse(time);
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            cal.add(scope, counts);
            return df.format(cal.getTime());
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * date2比date1多的天数
     * <pre>
     * 只是通过日期来进行比较两个日期的相差天数的比较，没有精确到相差到一天的时间。如果是只是纯粹通过日期（年月日）来比较
     *
     * 2015-1-1 21:21:28 和 2015-1-2 1:21:28，这两个的差是1
     * </pre>
     *
     * @param date1
     * @param date2
     * @return
     */
    public static long getDistanceDays(Date date1, Date date2) {
        if(null==date1 || null==date2){
            throw new IllegalArgumentException("the date can not be null");
        }
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);

        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        int day1 = cal1.get(Calendar.DAY_OF_YEAR);
        int day2 = cal2.get(Calendar.DAY_OF_YEAR);

        int year1 = cal1.get(Calendar.YEAR);
        int year2 = cal2.get(Calendar.YEAR);
        if (year1 != year2) {
            //不同一年
            int timeDistance = 0;
            int firstYear = year1 > year2 ? year2 : year1;
            int lastYear = year1 > year2 ? year1 : year2;
            int fistDay = year1 > year2 ? day2 : day1;
            int lastDay = year1 > year2 ? day1 : day2;
            for (int i = firstYear; i < lastYear; i++) {
                if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0) {
                    //闰年
                    timeDistance += 366;
                } else {
                    //不是闰年
                    timeDistance += 365;
                }
            }
            int dis = timeDistance + lastDay - fistDay;
            if (year1 > year2) {
                return -dis;
            }
            return dis;
        } else {
            //同年
            return day2 - day1;
        }
    }

    /**
     * @see DateUtils#getDistanceDays(Date, Date)
     * @param date1  时间参数 1
     * @param date2  时间参数 2
     * @param format 时间格式
     * @return 相差天数(有正负值)
     */
    public static long getDistanceDays(String date1, String date2, DateFormatEnums format) throws Exception {
        if(StringUtils.isBlank(date1) || StringUtils.isBlank(date2)){
            throw new IllegalArgumentException("the date can not be null");
        }
        DateFormat df = new SimpleDateFormat(format.getExpression());
        return getDistanceDays(df.parse(date1), df.parse(date2));
    }

    /**
     * 通过时间秒毫秒数判断两个时间的间隔
     * <pre>
     * 直接通过计算两个日期的毫秒数，他们的差除以一天的毫秒数，即可得到我们想要的两个日期相差的天数
     * 是通过计算两个日期相差的毫秒数来计算两个日期的天数差的。有一个小问题，就是当他们相差小于24个小时的时候，它就不算一天了。
     *
     * 如下面的两个日期
     * 2015-1-1 21:21:28 和 2015-1-2 1:21:28，这两个的差是0
     * </pre>
     *
     * @param date1 时间1
     * @param date2 时间2
     * @return
     */
    public static long getDistanceDaysByMillisecond(Date date1, Date date2) {
        if(null==date1 || null==date2){
            throw new IllegalArgumentException("the date can not be null");
        }
        return ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24));
    }

    /**
     * @param date1  时间参数 1
     * @param date2  时间参数 2
     * @param format 时间格式
     * @return 相差天数
     * @see DateUtils#getDistanceDaysByMillisecond(Date, Date)
     */
    public static long getDistanceDaysByMillisecond(String date1, String date2, DateFormatEnums format) throws Exception {
        if(StringUtils.isBlank(date1) || StringUtils.isBlank(date2)){
            throw new IllegalArgumentException("the date can not be null");
        }
        DateFormat df = new SimpleDateFormat(format.getExpression());
        return getDistanceDaysByMillisecond(df.parse(date1), df.parse(date2));
    }

    /**
     * date2比date1多的月份数
     * <pre>
     * 只是通过日期来进行比较两个日期的相差天数的比较，没有精确到相差到一天的时间。如果是只是纯粹通过日期（年月日）来比较
     * </pre>
     *
     * @param date1
     * @param date2
     * @return
     */
    public static long getDistanceMonths(Date date1, Date date2) {
        if(null==date1 || null==date2){
            throw new IllegalArgumentException("the date can not be null");
        }
        Calendar bef = Calendar.getInstance();
        Calendar aft = Calendar.getInstance();
        bef.setTime(date1);
        aft.setTime(date2);
        long result = aft.get(Calendar.MONTH) - bef.get(Calendar.MONTH);
        long month = (aft.get(Calendar.YEAR) - bef.get(Calendar.YEAR)) * 12;
        return result + month;
    }

    /**
     * date2比date1多的月份数
     * <pre>
     * 只是通过日期来进行比较两个日期的相差天数的比较，没有精确到相差到一天的时间。如果是只是纯粹通过日期（年月日）来比较
     * </pre>
     *
     * @param date1
     * @param date2
     * @return
     */
    public static long getDistanceMonths(String date1, String date2, DateFormatEnums format) throws Exception {
        if(StringUtils.isBlank(date1) || StringUtils.isBlank(date2)){
            throw new IllegalArgumentException("the date can not be null");
        }
        DateFormat df = new SimpleDateFormat(format.getExpression());
        return getDistanceMonths(df.parse(date1), df.parse(date2));
    }

    /**
     * 时间转换成具体的时间戳
     * @param date 源时间
     * @param timeUnit 目标时间戳时间单位
     * @param format date的时间格式
     * @return
     */
    public static String dateToStamp(String date, TimeUnit timeUnit, DateFormatEnums format) {
        if(StringUtils.isBlank(date)){
            return null;
        }
        if(!TimeUnit.SECONDS.equals(timeUnit) && !TimeUnit.MILLISECONDS.equals(timeUnit)){
            logger.warn("时间单位不正确，可能会导致计算的时间不正确，请选用[TimeUnit.SECONDS]或[TimeUnit.MILLISECONDS]");
        }
        Date d = stringToDate(date, format);
        long ts = d.getTime();
        String res;
        if(TimeUnit.SECONDS.equals(timeUnit)){
            res = String.valueOf(ts / 1000);
        }else{
            res = String.valueOf(ts);
        }
        return res;
    }

    /**
     * 时间戳转换为时间
     * @param stamp 时间戳
     * @param timeUnit 时间单位（TimeUnit.SECONDS或者TimeUnit.MILLISECONDS）
     * @param format 目标的时间格式
     * @return
     */
    public static String stampToDate(String stamp, TimeUnit timeUnit, DateFormatEnums format) {
        if(StringUtils.isBlank(stamp) || null==timeUnit){
            return null;
        }
        if(!TimeUnit.SECONDS.equals(timeUnit) && !TimeUnit.MILLISECONDS.equals(timeUnit)){
            logger.warn("时间单位不正确，可能会导致计算的时间不正确，请选用[TimeUnit.SECONDS]或[TimeUnit.MILLISECONDS]");
        }
        long lt = Long.parseLong(stamp);
        Date date;
        if(TimeUnit.SECONDS.equals(timeUnit)){
            date = new Date(lt*1000);
        }else{
            date = new Date(lt);
        }
        return dateToString(date,format);
    }
}